package ldh.im.fxbase.service;

import javafx.application.Platform;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import ldh.im.fxbase.util.FxThreadUtil;
import ldh.im.fxbase.util.ThreadPoolUtil;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.stream.Collectors;

import static org.bytedeco.ffmpeg.global.avutil.av_log_set_level;

public class AudioRecordService {

    private static final Logger LOGGER = LoggerFactory.getLogger(AudioRecordService.class);

    private static final AudioFormat.Encoding AUDIO_ENCODE = AudioFormat.Encoding.PCM_SIGNED;

    private Date recordStartDate = null;
    private LongProperty recordTimeProperty = new SimpleLongProperty(0);

    private String saveDir;
    private String audioFile;
    private String userId;

    private volatile boolean pause = false;

    private TargetDataLine line = null;

    private FFmpegFrameRecorder recorder = null;

    public AudioRecordService(String userId, String localDir) {
        this.userId = userId;
        this.saveDir = localDir;
    }

    public void startRecord() throws Exception {
        initAudioFileName();
        av_log_set_level(4);
        String[] paths = new String[] {this.saveDir, this.audioFile};
        String outPutPath = Arrays.stream(paths).collect(Collectors.joining(File.separator));
        recorder = new FFmpegFrameRecorder(outPutPath, 0);

//        recorder.setAudioOption("crf", "0");
//        recorder.setAudioQuality(0);
//        recorder.setAudioBitrate(192000);
//        recorder.setSampleRate(44100);
        recorder.setFormat("m4a"); // 设置输出格式为 m4a
        recorder.setSampleRate(48000);
        recorder.setAudioChannels(2);
        // AAC
//        recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC_LATM);
        recorder.start();

        ThreadPoolUtil.submit(()-> getAudioRecordThread().run());
        this.recordStartDate = new Date();
    }

    protected Runnable getAudioRecordThread() {
        return ()-> {
            try {
                if(pause) return;
                AudioFormat format = getAudioFormat();
                line = (TargetDataLine) AudioSystem.getLine(new DataLine.Info(TargetDataLine.class, format));
                line.open(format);
                line.start();

                int sampleRate = (int) format.getSampleRate();
                int numChannels = format.getChannels();
                byte[] buffer = new byte[sampleRate * numChannels];
                int i = 0;
                while (!pause) {
                    int nBytesRead = 0;
                    while (nBytesRead == 0) {
                        nBytesRead = line.read(buffer, 0, line.available());
                    }
                    if (i++%10 == 0) {
                        this.calcTime();
                    }
                    int nSamplesRead = nBytesRead / 2;
                    short[] samples = new short[nSamplesRead];
                    ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
                    ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, nSamplesRead);
                    recorder.recordSamples(sampleRate, numChannels, sBuff);
                }
            } catch (Exception e) {
                LOGGER.error("获取语音数据失败", e);
            }
        };
    }

    // 停止录制
    public void stopRecord() throws Exception {
        pause = false;
        try {
            if (recorder != null) {
                // 仅在started状态下调用pause为合理状态切换
                if (recordStartDate != null) {
                    this.calcTime();
                }
                recordStartDate = null;
                LOGGER.info("[audio record pause]");
                recorder.stop();
            }
        } finally {
            if (line != null) {
                line.stop();
                line.close();
            }
        }

    }

    // 获取录制声音的毫秒数
    public long getRecordTime() {
        return recordTimeProperty.get();
    }

    public LongProperty getRecordTimeProperty() {
        return recordTimeProperty;
    }

    public void calcTime() {
        long realTime = (new Date().getTime() - recordStartDate.getTime());
        FxThreadUtil.runLater(()->recordTimeProperty.set(realTime));
    }

    public String getAudioFile() {
        return this.saveDir + audioFile;
    }

    public AudioFormat getAudioFormat() {
        float sampleRate = 8000;     // 8000,11025,16000,22050,44100
        int sampleSizeInBits = 16;    // 8,16
        int channels = 1;             // 1,2
        int frameSize = 2;
        boolean signed = true;        // true,false
        boolean bigEndian = false;    // true,false
        return new AudioFormat(AUDIO_ENCODE, sampleRate, sampleSizeInBits, channels, frameSize, sampleRate,  bigEndian);
    }

    private void initAudioFileName() {
        String fileName = getFileName();
        String[] paths = new String[] {"capture", this.userId, "audio", fileName + ".m4a"};
        this.audioFile = File.separator + Arrays.stream(paths).collect(Collectors.joining(File.separator));
        this.initDir();
    }

    private String getFileName() {
        Date now = new Date();
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssS");
        return dateFormat.format(now);
    }

    private void initDir() {
        String[] paths = new String[] {this.saveDir, "capture", this.userId, "audio"};
        String dir = Arrays.stream(paths).collect(Collectors.joining(File.separator));
        File dirFile = new File(dir);
        if (!dirFile.exists()) {
            dirFile.mkdirs();
        }
    }
}
